package com.lnj.utils;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;


//TODO: java 通过文件内容的魔术字检查文件类型
/**
 * @program: shopping147_cloud
 * @description:
 * @author: lnj
 * @create: 2025-07-22 19:35
 */
public class FileTypeValidator {
    // 允许的文件类型
    private static final Set<String> ALLOWED_MIME_TYPES = new HashSet<>(Arrays.asList(
            "image/jpeg",
            "image/png",
            "image/gif",
            "application/pdf",
            "application/msword", // .doc
            "application/vnd.openxmlformats-officedocument.wordprocessingml.document", // .docx
            "application/vnd.ms-excel", // .xls
            "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" // .xlsx
    ));

    // 允许的文件扩展名
    private static final Set<String> ALLOWED_EXTENSIONS = new HashSet<>(Arrays.asList(
            "jpg", "jpeg", "png", "gif",
            "pdf",
            "doc", "docx",
            "xls", "xlsx"
    ));

    // 魔术字映射
    private static final byte[][] JPEG_MAGIC = {new byte[]{(byte) 0xFF, (byte) 0xD8, (byte) 0xFF}};
    private static final byte[][] PNG_MAGIC = {new byte[]{(byte) 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}};
    private static final byte[][] GIF_MAGIC = {
            "GIF87a".getBytes(),
            "GIF89a".getBytes()
    };
    private static final byte[][] PDF_MAGIC = {new byte[]{0x25, 0x50, 0x44, 0x46}};
    private static final byte[][] DOC_MAGIC = {new byte[]{(byte) 0xD0, (byte) 0xCF, 0x11, (byte) 0xE0, (byte) 0xA1, (byte) 0xB1, 0x1A, 0x00}};
    private static final byte[][] XLS_MAGIC = {new byte[]{(byte) 0xD0, (byte) 0xCF, 0x11, (byte) 0xE0, (byte) 0xA1, (byte) 0xB1, 0x1A, 0x00}};
    private static final byte[][] DOCX_XLSX_MAGIC = {new byte[]{0x50, 0x4B, 0x03, 0x04}};


    /**
     * 验证文件类型是否允许
     * @param contentType 文件类型
     * @param filename 文件名
     * @return 验证通过返回true, 否则返回false
     */

    /**
     * 验证文件类型是否允许
     * @param contentType 文件类型
     * @param filename 文件名
     * @param is 文件输入流
     * @return 验证通过返回true, 否则返回false
     */
    public static boolean isValidFileType(String contentType, String filename,InputStream is) {
        if (contentType == null || filename == null || contentType.isEmpty() || filename.isEmpty()) {
            return false; // 文件名或文件类型为空，返回false
        }
        // 1. 检查文件类型是否在允许列表中
        if (!ALLOWED_MIME_TYPES.contains(contentType.toLowerCase())) {
            return false;
        }
        // 2. 检查文件扩展名是否在允许列表中
        String fileExtension = getFileExtension(filename);
        if (fileExtension == null || !ALLOWED_EXTENSIONS.contains(fileExtension.toLowerCase())) {
            return false;
        }
        // 3. 检查文件的魔术字
        return checkMagicNumber(is, fileExtension.toLowerCase());
    }

    private static String getFileExtension(String filename) {
        int dotIndex = filename.lastIndexOf('.');
        if (dotIndex > 0 && dotIndex < filename.length() - 1) {
            return filename.substring(dotIndex + 1);
        }
        return null;
    }

    /**
     * 检查文件的魔术字
     * @param is 文件输入流
     * @param fileExtension 文件扩展名
     * @return 魔术字匹配返回true, 否则返回false
     * @throws IOException 读取文件时出错
     */
    private static boolean checkMagicNumber(InputStream is, String fileExtension){
        byte[] buffer = new byte[8];
        int bytesRead = 0;
        try {
            bytesRead = is.read(buffer);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        if (bytesRead < 1) {
            return false;
        }

        switch (fileExtension) {
            case "jpg":
            case "jpeg":
                return matchesMagic(buffer, JPEG_MAGIC);
            case "png":
                return matchesMagic(buffer, PNG_MAGIC);
            case "gif":
                return matchesMagic(buffer, GIF_MAGIC);
            case "pdf":
                return matchesMagic(buffer, PDF_MAGIC);
            case "doc":
                return matchesMagic(buffer, DOC_MAGIC);
            case "xls":
                return matchesMagic(buffer, XLS_MAGIC);
            case "docx":
            case "xlsx":
                return matchesMagic(buffer, DOCX_XLSX_MAGIC);
            default:
                return false;
        }
    }

    /**
     * 检查缓冲区中的字节是否匹配任何魔术字
     * @param buffer 字节缓冲区
     * @param magicNumbers 魔术字数组
     * @return 匹配返回true, 否则返回false
     */
    private static boolean matchesMagic(byte[] buffer, byte[][] magicNumbers) {
        for (byte[] magic : magicNumbers) {
            if (buffer.length >= magic.length) {
                boolean match = true;
                for (int i = 0; i < magic.length; i++) {
                    if (buffer[i] != magic[i]) {
                        match = false;
                        break;
                    }
                }
                if (match) {
                    return true;
                }
            }
        }
        return false;
    }
}
